package cn.jarkata.commons.utils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 反射工具类
 */
public class ReflectionUtils {
    private static final ConcurrentHashMap<String, List<Field>> ALL_FIELD_CACHE_MAP = new ConcurrentHashMap<>(256);
    private static final ConcurrentHashMap<String, Field> FIELD_CACHE_MAP = new ConcurrentHashMap<>(256);
    private static final ConcurrentHashMap<String, List<Method>> METHOD_LIST_CACHE_MAP = new ConcurrentHashMap<>(256);

    public static Method getDeclaredMethod(Object obj, String methodName) {
        Objects.requireNonNull(obj, "Object Null");
        Objects.requireNonNull(methodName, "MethodName Null");
        if (obj instanceof Class<?>) {
            return getDeclaredMethod((Class<?>) obj, methodName, null);
        } else {
            return getDeclaredMethod(obj.getClass(), methodName, null);
        }
    }

    public static Method getDeclaredMethod(Object obj, String methodName, Class<?>[] parameterType) {
        Objects.requireNonNull(obj, "Obj Null");
        Objects.requireNonNull(methodName, "MethodName Null");
        if (obj instanceof Class) {
            return getDeclaredMethod((Class<?>) obj, methodName, parameterType);
        } else {
            return getDeclaredMethod(obj.getClass(), methodName, parameterType);
        }
    }

    public static Method getDeclaredMethod(Class<?> objClass, String methodName, Class<?>[] parameterType) {
        Objects.requireNonNull(objClass, "Obj Null");
        Objects.requireNonNull(methodName, "MethodName Null");
        try {
            if (isRootClass(objClass)) {
                return null;
            }
            if (Objects.isNull(parameterType)) {
                return objClass.getDeclaredMethod(methodName);
            }
            return objClass.getDeclaredMethod(methodName, parameterType);
        } catch (Exception e) {
            return getDeclaredMethod(objClass.getSuperclass(), methodName, parameterType);
        }
    }

    /**
     * 调用指定的方法
     *
     * @param obj        对象
     * @param method     方法名
     * @param parameters 参数集合
     * @return 方法执行结果
     */
    public static Object invokeMethod(Object obj, String method, Object[] parameters) {
        if (Objects.isNull(obj) || Objects.isNull(parameters)) {
            return null;
        }
        Method declaredMethod = getDeclaredMethod(obj, method, Arrays.stream(parameters).map(Object::getClass).toArray(Class[]::new));
        try {
            declaredMethod.setAccessible(true);
            return declaredMethod.invoke(obj, parameters);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 调用指定的方法
     *
     * @param obj    对象
     * @param method 方法名
     * @return 方法执行结果
     */
    public static Object invokeMethod(Object obj, String method) {
        if (Objects.isNull(obj)) {
            return null;
        }
        Method declaredMethod = getDeclaredMethod(obj, method, null);
        if (Objects.isNull(declaredMethod)) {
            return null;
        }
        try {
            declaredMethod.setAccessible(true);
            return declaredMethod.invoke(obj);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据方法名查找对应的方法
     *
     * @param obj        对象
     * @param methodName 方法名
     * @return 方法集合
     */
    public static List<Method> getDeclaredMethodList(Object obj, String methodName) {
        if (Objects.isNull(obj)) {
            return new ArrayList<>(0);
        }
        if (obj instanceof Class<?>) {
            return getDeclaredMethodList((Class<?>) obj, methodName);
        }
        return getDeclaredMethodList(obj.getClass(), methodName);
    }

    /**
     * 根据Class对象查找对应的方法
     *
     * @param clazz      clazz对象
     * @param methodName 方法名
     * @return 方法集合
     */
    public static List<Method> getDeclaredMethodList(Class<?> clazz, String methodName) {
        if (Objects.isNull(clazz)) {
            return new ArrayList<>(0);
        }
        List<Method> methodList = getMethodList(clazz);
        return methodList.stream().filter(method -> method.getName().equals(methodName)).collect(Collectors.toList());
    }


    public static List<Method> getMethodList(Class<?> clazz) {
        List<Method> methodList = METHOD_LIST_CACHE_MAP.getOrDefault(clazz.getName(), new ArrayList<>(0));
        if (!methodList.isEmpty()) {
            return methodList;
        } else {
            methodList = new ArrayList<>();
        }
        makeMethod(methodList, clazz);
        if (!methodList.isEmpty()) {
            METHOD_LIST_CACHE_MAP.put(clazz.getName(), methodList);
        }
        return methodList;
    }


    protected static void makeMethod(List<Method> methodList, Class<?> clazz) {
        if (isRootClass(clazz)) {
            return;
        }
        Method[] declaredMethods = clazz.getDeclaredMethods();
        if (declaredMethods.length > 0) {
            methodList.addAll(Arrays.asList(declaredMethods));
        }
        makeMethod(methodList, clazz.getSuperclass());
    }

    public static List<Field> getFieldList(Class<?> objClazz) {
        if (Objects.isNull(objClazz)) {
            return new ArrayList<>(0);
        }
        List<Field> fieldList = ALL_FIELD_CACHE_MAP.getOrDefault(objClazz.getName(), new ArrayList<>(0));
        if (Objects.nonNull(fieldList) && !fieldList.isEmpty()) {
            return fieldList;
        }
        fieldList = new ArrayList<>();
        makeField(fieldList, objClazz);
        if (!fieldList.isEmpty()) {
            ALL_FIELD_CACHE_MAP.put(objClazz.getName(), fieldList);
        }
        return fieldList;
    }

    protected static void makeField(List<Field> fieldList, Class<?> clazz) {
        if (isRootClass(clazz)) {
            return;
        }
        Field[] declaredFields = clazz.getDeclaredFields();
        if (declaredFields.length > 0) {
            fieldList.addAll(Arrays.asList(declaredFields));
        }
        Class<?> superclass = clazz.getSuperclass();
        makeField(fieldList, superclass);
    }

    public static void setFieldValue(Field distDeclaredField, Object distObj, Object fieldValue) {
        if (Objects.isNull(distDeclaredField) || Objects.isNull(fieldValue)) {
            return;
        }
        try {
            distDeclaredField.setAccessible(true);
            distDeclaredField.set(distObj, fieldValue);
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    public static Field getDeclaredField(Object obj, String fieldName) {
        if (Objects.isNull(obj)) {
            return null;
        }
        if (obj instanceof Class) {
            return getDeclaredField((Class<?>) obj, fieldName);
        }
        return getDeclaredField(obj.getClass(), fieldName);
    }


    public static Field getDeclaredField(Class<?> objClazz, String fieldName) {
        String cacheKey = buildCacheKey(objClazz, fieldName);
        Field declaredField = FIELD_CACHE_MAP.get(cacheKey);
        if (Objects.nonNull(declaredField)) {
            return declaredField;
        }
        declaredField = getInnerDeclaredField(objClazz, fieldName);
        if (Objects.nonNull(declaredField)) {
            FIELD_CACHE_MAP.put(cacheKey, declaredField);
        }
        return declaredField;
    }

    /**
     * 获取objClass对象指定的字段
     *
     * @param objClazz  class对象
     * @param fieldName 字段名
     * @return Field对象
     */
    protected static Field getInnerDeclaredField(Class<?> objClazz, String fieldName) {
        if (isRootClass(objClazz)) {
            return null;
        }
        Field declaredField;
        try {
            declaredField = objClazz.getDeclaredField(fieldName);
        } catch (Throwable ex) {
            declaredField = getDeclaredField(objClazz.getSuperclass(), fieldName);
        }
        return declaredField;
    }

    /**
     * 判断是否为根对象
     *
     * @param objClazz Class对象
     * @return objClazz 如果为空,返回true,如果为根对象Object,也返回true
     */
    protected static boolean isRootClass(Class<?> objClazz) {
        if (Objects.isNull(objClazz)) {
            return true;
        }
        return Object.class.getName().equals(objClazz.getName());
    }

    private static String buildCacheKey(Class<?> objClazz, String fieldName) {
        return objClazz.getName() + fieldName;
    }

    /**
     * @param declaredField 字段
     * @param obj           对象
     * @return 字段值
     */
    public static Object getFieldValue(Field declaredField, Object obj) {
        try {
            if (Objects.isNull(declaredField)) {
                return null;
            }
            declaredField.setAccessible(true);
            return declaredField.get(obj);
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    /**
     * 获取obj对象中fieldName字段的值
     *
     * @param obj       对象
     * @param fieldName 属性名称
     * @return 该{fieldName}属性的值
     */
    public static Object getFieldValue(Object obj, String fieldName) {
        try {
            Field declaredField = getDeclaredField(obj, fieldName);
            return getFieldValue(declaredField, obj);
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    /**
     * 将对象数据转换为Map结构
     *
     * @param obj 对象
     * @return key/value形式的数据
     */
    public static Map<String, Object> toObjectMap(Object obj) {
        if (Objects.isNull(obj)) {
            return new LinkedHashMap<>(0);
        }
        if (obj instanceof Map) {
            return (Map<String, Object>) obj;  // unchecked assignment
        }
        List<Field> allField = getFieldList(obj.getClass());
        List<Field> fieldList = allField.stream().filter(field -> field.getModifiers() != Modifier.STATIC && field.getModifiers() != Modifier.FINAL).collect(Collectors.toList());
        Map<String, Object> dataMap = new LinkedHashMap<>(allField.size());
        for (Field field : fieldList) {
            Object fieldValue = getFieldValue(field, obj);
            dataMap.put(field.getName(), fieldValue);
        }
        return dataMap;
    }

}